package com.qf.framwork.base;

import com.alibaba.fastjson.JSON;
import com.qf.framwork.annotation.ResponseBody;
import com.qf.framwork.cases.ParamsCases;
import com.qf.framwork.page.Page;
import com.qf.framwork.page.PageHelper;
import com.qf.framwork.page.PageThreadLocal;
import com.qf.framwork.utils.PropertiesUtils;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * description:
 * author: Ken
 * 公众号：Java架构栈
 */
public class BaseServlet extends HttpServlet {

    /**
     * 父类模板的流程方法
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("父类的Service触发了！");
        //获取子类对应的Class对象
        Class cls = this.getClass();
        //获取参数 - 架构师和开发者之间的约定
        String m = req.getParameter("method");
        if (m == null) {
            //违背了约定，没有携带该参数
            throw new ServletException("请求必须携带一个名为method的参数！");
        }

        //找到一个名字为m的方法
        //通过反射找到匹配的方法
        Method runMethod = null;
        //通过反射，查找需要执行的方法
        Method[] methods = cls.getDeclaredMethods();
        for (Method method : methods) {
            if (method.getName().equals(m)) {

                //获取该方法的修饰符
                int modifiers = method.getModifiers();
                if (Modifier.isPrivate(modifiers)) {
                    throw new ServletException(method.getName() + "方法不能私有！");
                }

                //需要执行的方法为该method - 这种方式无需去匹配参数类型
                runMethod = method;
                break;
            }
        }

        //如果未找到匹配的方法
        if (runMethod == null) {
            throw new ServletException("未在" + cls.getName() + "中找到" + m + "的方法！");
        }

        //分页的处理
        Page page = pageHelper(runMethod, req);

        try {
            //根据方法的形参，组装一个实参的数组
            Object[] params = getMethodParams(runMethod, req, resp);

            //执行该方法
            Object result = runMethod.invoke(this, params);

            //额外处理下分页的对象
            if (page != null){
                req.setAttribute("page", page);
            }

            //方法的返回值处理
            methodResultHandler(result, runMethod, req, resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 分页预处理的方法
     * @return
     */
    private Page pageHelper(Method m, HttpServletRequest request){
        //判断当前的业务是否需要分页
        PageHelper pageHelper = m.getAnnotation(PageHelper.class);
        if (pageHelper != null) {
            //说明当前的请求需要分页
            String pageStr = request.getParameter("page");
            String pageSizeStr = request.getParameter("pageSize");
            //当前页
            int page = 1;
            //每页默认多少条
            int pageSize = 10;

            if (pageStr != null) {
                page = Integer.parseInt(pageStr);
            }
            if (pageSizeStr != null) {
                pageSize = Integer.parseInt(pageSizeStr);
            }

            //构建Page对象
            Page pageObj = new Page()
                    .setPage(page)
                    .setPageSize(pageSize);

            //从request对象中获得请求的url
            String url = request.getRequestURI();
            pageObj.setUrl(url + "?method=" + request.getParameter("method"));

            //将Page对象放入ThreadLocal中
            PageThreadLocal.setPage(pageObj);
            return pageObj;
        }
        return null;
    }


    /**
     * 根据Method的形参,组装当前方法的实际参数列表
     *
     *              //根据参数的类型判断 - 传统模式
     *             if (parameter.getType() == HttpServletRequest.class) {
     *                 //将request对象封装进实际参数中
     *                 params.add(request);
     *             } else if (parameter.getType() == HttpServletResponse.class) {
     *                 //将response对象封装进实际参数中
     *                 params.add(response);
     *             } else if (parameter.getType() == HttpSession.class) {
     *                 //将session对象封装到实际参数中
     *                 params.add(request.getSession());
     *             } else if (parameter.getType() == PrintWriter.class) {
     *                 //将PrintWriter对象封装到实际参数中
     *                 params.add(response.getWriter());
     *             } else if (parameter.getType() == String.class) {
     *                 //有可能需要从请求参数中获取值
     *                 String pValue = request.getParameter(parameter.getName());
     *                 params.add(pValue);
     *             } else {
     *                 //其他类型的参数，全部给null
     *                 params.add(null);
     *             }
     * @return
     */
    private Object[] getMethodParams(Method m, HttpServletRequest request, HttpServletResponse response) throws Exception {

        //上传解析
        Map<String, FileItem> itemMap = parseUploader(request);

        //组装实参列表的集合
        List<Object> params = new ArrayList<>();

        // 1、获得当前方法的形参列表
        Parameter[] parameters = m.getParameters();
        //如果没有形参列表，则返回null
        if (parameters == null) return null;
        // 2、遍历当前方法的形参列表
        for (Parameter parameter : parameters) {

            //策略模式 + 转换器
            //形参的类型
            Class<?> typeCls = parameter.getType();
            System.out.println("当前形参的类型：" + typeCls.getName());
            //通过形参的类型，获取转换器的全限定路径名
            String casesPath = PropertiesUtils.getProperties(typeCls.getName());
            //----单独处理实体类的类型---
            if (casesPath == null) {
                //按照实体来处理 - typeCls
                //基于实体类的类型创建一个实体类的对象
                Object entity = typeCls.newInstance();

                //获得当前实体中的所有属性
                Field[] fields = typeCls.getDeclaredFields();
                //循环所有属性
                for (Field field : fields) {
                    //授权
                    field.setAccessible(true);

                    //获得属性的类型和名字
                    Class<?> fieldType = field.getType();
                    String fieldName = field.getName();

                    //属性需要设置的值
                    Object fieldValue = null;

                    //通过实体类中 每个属性的类型 获取对应的参数转换器全路径限定名
                    String casesPath2 = PropertiesUtils.getProperties(fieldType.getName());
                    if (casesPath2 != null) {
                        //通过全路径限定名 - 反射 - 获取类型转换器
                        ParamsCases paramsCases = (ParamsCases) Class.forName(casesPath2).newInstance();
                        //调用类型转换器 - 获取当前的参数值
                        fieldValue = paramsCases.getParams(m, fieldName, itemMap, request, response);
                    }

                    //通过反射将该值设置给实体类的当前属性
                    field.set(entity, fieldValue);
                }

                params.add(entity);
                continue;
            }
            //----单独处理实体类的类型---

            //当前已经获得转换器路径，实例化该转换器  多态
            ParamsCases paramsCases = (ParamsCases) Class.forName(casesPath).newInstance();
            //通过转换器获得实参
            Object param = paramsCases.getParams(m, parameter.getName(), itemMap, request, response);
            //将转换器的实参放入实参列表中
            params.add(param);
        }

        //3、将实际参数列表转换为数组，并且返回
        return params.toArray();
    }

    /**
     * 解析上传请求
     * @return
     */
    private Map<String, FileItem> parseUploader(HttpServletRequest request){
        //判断当前是否为上传请求
        String method = request.getMethod();
        //判断是否为post请求
        if (!method.equalsIgnoreCase("post"))
            return null;

        //判断当前的请求类型
        String contentType = request.getContentType();
        System.out.println("---------->请求的ContentType:" + contentType);

        //判断是否为上传类型
        if (!contentType.startsWith("multipart/"))
            return null;

        //当前是上传的类型
        DiskFileItemFactory fileItemFactory = new DiskFileItemFactory();
        ServletFileUpload servletFileUpload = new ServletFileUpload(fileItemFactory);
        //设置文件上传的路径编码格式
        servletFileUpload.setHeaderEncoding("utf-8");

        List<FileItem> items = null;
        try {
            items = servletFileUpload.parseRequest(request);
        } catch (FileUploadException e) {
            e.printStackTrace();
        }


        Map<String, FileItem> itemMap = new HashMap<>();
        for (FileItem item : items) {
            itemMap.put(item.getFieldName(), item);
        }
        return itemMap.size() > 0 ? itemMap : null;
    }

    /**
     * 方法的返回值处理
     */
    private void methodResultHandler(Object result, Method method, HttpServletRequest req, HttpServletResponse resp) throws Exception {
        //如果方法返回值是null，则啥都不用干
        if (result == null) return;

        //判断当前方法上的注解
        ResponseBody responseBody = method.getAnnotation(ResponseBody.class);
        if (responseBody != null) {
            //说明当前的返回值需要直接通过PrintWirter方法响应给客户端，无需进行页面跳转
            resp.setContentType("application/json;charset=utf-8");
            PrintWriter out = resp.getWriter();
            //将 result -> json字符串
            result = (result.getClass() == String.class) ? result : JSON.toJSONString(result);
            out.print(result);
            out.close();
            return;
        }

        //判断方法的返回值
        if (result.getClass() == String.class) {
            //将方法的返回值强制转换成jsp的路径
            String jspPath = (String) result;

            //判断当前路径是否需要重定向
            if (jspPath.startsWith("redirect:")) {
                //当前需要重定向
                resp.sendRedirect(jspPath.split(":")[1]);
            } else {
                //进行转发
                req.getRequestDispatcher(jspPath).forward(req, resp);
            }
        }
    }
}
